package com.mpgame.common.handler;

import com.mpgame.common.proto.*;
import com.mpgame.common.network.GrpcClient;
import com.mpgame.common.connection.GrpcConnectionPool;
import com.mpgame.common.utils.Log;

import com.google.protobuf.ByteString;
import io.netty.channel.ChannelHandlerContext;

import java.util.function.Function;

public final class GrpcCallHandler {

    @FunctionalInterface
    public interface StubBuilder<S> {
        S build(GrpcClient cli);
    }

    @FunctionalInterface
    public interface RpcInvoker<S, T, R>{
        R invoke(S stub, T req) throws Exception;
    }
    
    /**
     * gRPC 调用模板
     * 
     * <p>流程：从连接池获取连接 -> 解析请求 -> 构建 stub -> 发起 RPC 调用 -> 判错/回包 -> 归还连接。</p>
     * 
     * @param <T>               解析后的请求类型（如LoginReq）
     * @param <R>               RPC 返回响应类型（如LoginRsp）
     * @param <S>               gRPC Stub类型（如VerifyServerServiceGrpc.VerifyServerServiceBlockingStub）
     * @param ctx               Netty 上下文，用于回写 TCP 响应给客户端
     * @param pkt               客户端发来的包
     * @param pool              gRPC 连接池
     * @param rspCmdId          回包时的 CmdId
     * @param parser            解析GamePacket.Data的解析器
     * @param stubBuilder       基于借出 GrpcClient 构建具体服务stub
     * @param rpcInvoker        使用 stub 与请求对象发器具体RPC调用，返回响应对象R
     * @param errMsgExtractor   从 RPC 响应 R 中提取错误文案：若Null/空串表示成功，否则失败
     * @param successPayload    成功时返回给客户端的包
     * @return true 业务处理成功并已回包；false 业务处理失败或发送错误
    */
    public static <T, R, S> boolean invoke(
        ChannelHandlerContext ctx,
        GamePacket pkt,
        GrpcConnectionPool pool,
        CmdId rspCmdId,
        Function<ByteString, T> parser,
        StubBuilder<S> stubBuilder,
        RpcInvoker<S, T, R> rpcInvoker,
        Function<R, String> errMsgExtractor,
        Function<R, com.google.protobuf.Message> successPayload
    ){
        GrpcClient cli = null;
        try{
            cli = pool.borrowConnection();
            if(cli == null || !cli.isHealthy()){
                sendErrRsp(ctx, rspCmdId, "服务暂时不可用");
                return false;
            }

            final T req;
            try{
                req = parser.apply(pkt.getData());
                if(req == null){
                    sendErrRsp(ctx, rspCmdId, "请求格式错误");
                    return false;
                }
            }catch(Exception e){
                sendErrRsp(ctx, rspCmdId, "请求解析异常");
                return false;
            }

            final S stub = stubBuilder.build(cli);
            final R rsp = rpcInvoker.invoke(stub, req);
            if(rsp == null){
                sendErrRsp(ctx, rspCmdId, "服务调用失败");
                return false;
            }

            final String err = errMsgExtractor.apply(rsp);
            if(err != null && !err.isEmpty()){
                sendErrRsp(ctx, rspCmdId, err);
                return false;
            }

            final com.google.protobuf.Message payload = successPayload.apply(rsp);
            if(rsp instanceof Empty || rspCmdId == CmdId.NONE){
                Log.info("gRPC调用返回客户端为empty或rspCmdId==CmdId.NONE，因此不发送回应，由其他逻辑来处理回应发送！");
            }
            else{
                sendSuccessRsp(ctx, rspCmdId, payload);
            }
            
            return true;
        }
        catch(io.grpc.StatusRuntimeException e){
            switch(e.getStatus().getCode()){
                case UNAVAILABLE -> sendErrRsp(ctx, rspCmdId, "服务暂时不可用");
                case DEADLINE_EXCEEDED -> sendErrRsp(ctx, rspCmdId, "服务调用超时");
                default -> sendErrRsp(ctx, rspCmdId, "服务调用异常");
            }
            return false;
        }
        catch(Exception e){
            sendErrRsp(ctx, rspCmdId, "请求处理异常" + e.getMessage());
            return false;
        }
        finally{
            if(cli != null){
                pool.returnConnection(cli);
            }
        }
    }

    public static void sendErrRsp(ChannelHandlerContext ctx, CmdId cmdId, String errMsg){
        try{
            GamePacket epkt = GamePacket.newBuilder()
                .setEc(ErrorCode.INVALID_PARAM)
                .setCmd(cmdId)
                .setData(ByteString.copyFromUtf8(errMsg))
                .build();
            
            ctx.writeAndFlush(epkt);
            Log.info("发送错误响应：" + errMsg);
        }
        catch(Exception e){
            Log.info("发送错误响应失败：" + e.getMessage());
        }
    }

    public static void sendSuccessRsp(ChannelHandlerContext ctx, CmdId cmdId, com.google.protobuf.Message rsp){
        try{
            GamePacket rpkt = GamePacket.newBuilder()
                .setEc(ErrorCode.SUCCESS)
                .setCmd(cmdId)
                .setData(rsp.toByteString())
                .build();
            ctx.writeAndFlush(rpkt);
            Log.info("发送成功响应：" + cmdId);
        }
        catch(Exception e){
            Log.info("发送成功响应失败：" + e.getMessage());
        }
    }

}
